package com.alcohol.auth.server.support.core;

import com.alcohol.auth.server.constant.CommonConstants;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 基于Redis实现的OAuth2授权服务类
 * @author LiXinYu
 * @date 2025/11/8
 */
@Service
public class RedisAuthorizationService implements OAuth2AuthorizationService {

    private final static Long TIMEOUT = 10L;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 保存OAuth2授权信息
     * @param authorization 授权信息对象
     */
    @Override
    public void save(OAuth2Authorization authorization) {
        Assert.notNull(authorization, "authorization cannot be null");
        OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization.getToken(OAuth2AuthorizationCode.class);

        // 检查授权对象是否包含state属性
        if (Objects.nonNull(authorization.getAttribute("state"))) {
            redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, authorization.getAttribute("state")), authorization, TIMEOUT, TimeUnit.MINUTES);
        }

        // 检查授权对象是否包含授权码
        if (Objects.nonNull(authorizationCode)) {
            OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
            Assert.notNull(authorizationCodeToken.getIssuedAt(), "Code发布时间不能为空！");
            // 默认五分钟有效
            long codeExpiresIn = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(), authorizationCodeToken.getExpiresAt());
            String redisKey = this.buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue());
            redisTemplate.opsForValue().set(redisKey, authorization, codeExpiresIn, TimeUnit.SECONDS);
        }

        // 判断授权对象是否包含访问令牌
        if (Objects.nonNull(authorization.getAccessToken())) {
            OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
            Assert.notNull(accessToken.getIssuedAt(), "AccessToken发布时间不能为空！");
            long accessTokenExpiresIn = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
            String redisKey = this.buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue());
            redisTemplate.opsForValue().set(redisKey, authorization, accessTokenExpiresIn, TimeUnit.SECONDS);
        }

        // 判断授权是否包含刷新令牌
        if (Objects.nonNull(authorization.getRefreshToken())) {
            OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
            Assert.notNull(refreshToken.getIssuedAt(), "RefreshToken发布时间不能为空！");
            long refreshTokenExpiresIn = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
            String redisKey = this.buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue());
            redisTemplate.opsForValue().set(redisKey, authorization, refreshTokenExpiresIn, TimeUnit.SECONDS);
        }
    }

    /**
     * 移除OAuth2授权信息
     * @param authorization 要移除的授权信息
     */
    @Override
    public void remove(OAuth2Authorization authorization) {

    }

    /**
     * 根据ID查询OAuth2授权信息
     * @param id 授权ID
     * @return 授权信息
     */
    @Override
    public OAuth2Authorization findById(String id) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据token和token类型查询OAuth2授权信息
     * @param token token值
     * @param tokenType token类型
     * @return 授权信息
     */
    @Override
    public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
        Assert.notNull(tokenType, "tokenType cannot be empty");
        return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token));
    }

    /**
     * 获取Token有效期
     * @param token token值
     * @param tokenType token类型
     * @return Token有效期
     */
    public Long getTokenExpire(String token, OAuth2TokenType tokenType) {
        return redisTemplate.opsForValue().getOperations().getExpire(buildKey(tokenType.getValue(), token));
    }

    /**
     * 构建key
     * @param tokenName token名称
     * @param token ID
     * @return 拼接后的key字符串
     */
    private String buildKey(String tokenName, String token) {
        return String.format("%s:%s:%s", CommonConstants.AUTH, tokenName, token);
    }
}
